package com.googlecode.dgwt.client.dijit.view.render.base;

import java.util.Collections;
import java.util.List;

import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.user.client.DOM;
import com.googlecode.dgwt.client.dijit.Dijit;
import com.googlecode.dgwt.client.dijit.View;
import com.googlecode.dgwt.client.dojo.Dojo;
import com.googlecode.dgwt.client.dojo.core.JsMap;
import com.googlecode.dgwt.client.dojo.util.NativeList;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public abstract class CellRenderBase extends RowRenderBase implements CellRender {
        
    private static final String CELL_FLAG_PROPERTY = "__dijitCellRender_cellFlag";

    private static final String ROW_CELLS_PROPERTY = "__dijitCellRender_rowCells";

    private static final String ROW_CELLS_READONLY_PROPERTY = "__dijitCellRender_rowCellsReadOnly";
    
    
    private int cellCount; 
    
    
    public CellRenderBase(View view) {
        super(view);
    }
    
    
    protected abstract Element createCellElement();

    protected void attachCell(Element rowElement, Element cellElement, int cellIndex) {
        DOM.insertChild(rowElement.<com.google.gwt.user.client.Element>cast(), 
                cellElement.<com.google.gwt.user.client.Element>cast(), cellIndex);
    }
    
    protected void detachCell(Element rowElement, Element cellElement, int cellIndex) {
        Node cellParent = cellElement.getParentNode();
        if (cellParent != null){
            cellParent.removeChild(cellElement);
            Dojo.destroy(cellElement);
        }
    }
        
    @Override
    public void internalAddRow(int rowIndex) {
        super.internalAddRow(rowIndex);
        Element rowElement = getRow(rowIndex);
        
        for (int cellIndex=0; cellIndex<getCellCount(); cellIndex++){
            internalAddCell(rowElement, cellIndex);
        }
    }
    
    protected void internalAddCell(Element rowElement, int cellIndex){
        Element cellElement = createCellElement();
        getCellList(rowElement).add(cellIndex, cellElement);
        cellElement.setPropertyBoolean(CELL_FLAG_PROPERTY, true);
        Dojo.addClass(cellElement, Dijit.getTheme().dijitCellName());
        attachCell(rowElement, cellElement, cellIndex);
    }

    protected void internalRemoveCell(Element rowElement, int cellIndex){
        List<Element> cellList = getCellList(rowElement);
        Element cellElement = cellList.get(cellIndex);
        getCellList(rowElement).remove(cellIndex);
        detachCell(rowElement, cellElement, cellIndex);
    }

    @Override
    protected void internalUpdateRow(int rowIndex, Object metaInf) {
        for (int cellIndex=0; cellIndex<getCellCount(); cellIndex++){
            updateCell(rowIndex, cellIndex);
        }
    }    
    
    @Override
    public void updateCells(int rowIndex, int rowCount, int cellIndex, int cellCount) {
        for (int row=0; row<rowCount; row++){
            for (int cell=0; cell<cellCount; cell++){
                updateCell(rowIndex + row, cellIndex + cell);
            }
        }
    }

    @Override
    public void addCell(int cellIndex) {
        for (int rowIndex=0; rowIndex<getRowCount(); rowIndex++){
            internalAddCell(getRow(rowIndex), cellIndex);
        }
        updateCells(cellIndex);
    }


    @Override
    public void removeCell(int cellIndex) {
        for (int rowIndex=0; rowIndex<getRowCount(); rowIndex++){
            internalRemoveCell(getRow(rowIndex), cellIndex);
        }
    }


    @Override
    public void updateCells(int cellIndex) {
        for (int rowIndex=0; rowIndex<getRowCount(); rowIndex++){
            updateCell(rowIndex, cellIndex);
        }
    }


    @Override
    public void updateCells(int cellIndex, int cellCount) {
        updateCells(0, getRowCount(), cellIndex, cellCount);
    }


    @Override
    public void updateCells(Object metaInf, int cellIndex) {
        if (metaInf == null){
            updateCells(cellIndex);
            return;
        }
        for (int rowIndex=0; rowIndex<getRowCount(); rowIndex++){
            if (metaInf.equals(getMetaInf(rowIndex))){
                updateCell(rowIndex, cellIndex);
            }
        }
    }

    public void setCellCount(int cellCount) {
        assert (cellCount >= 0);
        if (this.cellCount != cellCount){
            
            for (int rowIndex=0; rowIndex<getRowCount(); rowIndex++){
                Element rowElement = getRow(rowIndex);
                List<Element> cellList = getCellList(rowElement);
                while (cellList.size() < cellCount){
                    internalAddCell(rowElement, cellList.size());
                }
                while (cellList.size() > cellCount){
                    internalRemoveCell(rowElement, cellList.size()-1);
                }
            }            

            this.cellCount = cellCount;
        }
    }

    protected NativeList<Element> getCellList(Element rowElement){
        JsMap<NativeList<Element>> map = rowElement.cast();
        NativeList<Element> list = map.get(ROW_CELLS_PROPERTY);
        if (list == null){
            list = new NativeList<Element>();
            map.set(ROW_CELLS_PROPERTY, list);
        }
        return list;            
    }

    @Override
    public Element getCell(int rowIndex, int cellIndex) {
        return getCellList(getRow(rowIndex)).get(cellIndex);
    }

    @Override
    public int getCellIndex(Element cellElement) {
        Node node = cellElement;
        boolean cellFound = false;
        while (node != null) {
            if (cellFound){
                Object list;
                if ((list = node.<JsMap<Object>>cast().get(ROW_CELLS_PROPERTY)) != null){
                    if (list instanceof List<?>){
                        return ((List<?>) list).indexOf(cellElement);
                    }
                }
            }
            else if (node.<Element>cast().getPropertyBoolean(CELL_FLAG_PROPERTY)){
                cellFound = true;
                cellElement = node.cast();
            }
            node = node.getParentNode();
        }
        return -1;
    }

    @Override
    public List<Element> getRowCells(int rowIndex) {
        JsMap<List<Element>> map = getRow(rowIndex).cast();
        List<Element> list = map.get(ROW_CELLS_READONLY_PROPERTY);
        if (list == null){
            list = Collections.unmodifiableList(getCellList(map.<Element>cast()));
            map.set(ROW_CELLS_READONLY_PROPERTY, list);
        }
        return list;            
    }

    @Override
    public int getCellCount() {
        return cellCount;
    }
}
